/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.zonal;

import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.util.List;
import org.eclipse.imagen.JAI;
import org.eclipse.imagen.OperationDescriptorImpl;
import org.eclipse.imagen.ParameterBlockJAI;
import org.eclipse.imagen.ROI;
import org.eclipse.imagen.RenderedOp;
import org.eclipse.imagen.media.range.Range;
import org.eclipse.imagen.media.stats.Statistics.StatsType;
import org.eclipse.imagen.registry.RenderedRegistryMode;

/**
 * An <code>OperationDescriptor</code> describing the Zonal Statistics operation.
 *
 * <p>The ZonalStats operation takes in input a source image, an optional classifier image and a list of geometries on
 * which the selected statistics are calculated. These statistics are defined by the input {@link StatsType} array. The
 * calculations can NoData. It is important to remember that the classifier must be of integral data type.The possible
 * statistics are:
 *
 * <p>
 *
 * <ul>
 *   <li>Mean
 *   <li>Sum
 *   <li>Maximum
 *   <li>Minimum
 *   <li>Extrema
 *   <li>Variance
 *   <li>Standard Deviation
 *   <li>Histogram
 *   <li>Mode
 *   <li>Median
 * </ul>
 *
 * <p>It is important to remember that if the Median, Mode or Histogram operations must be executed, even their Bounds
 * and Bin numbers must be defined. The source can have all the possible JAI accepted data types. The statistical
 * calculations are performed on every tile with an adequate synchronization and stored inside an instance of the
 * {@link ZoneGeometry} class. For avoiding concurrency issues, the statistic calculation is done in a synchronized
 * block. The statistical results are returned by calling the getProperty() method. The calculation happens only the
 * first time for avoiding unnecessary loss of time.
 *
 * <p>The results are returned by the getProperty() method as a List<ZoneGeometry>. Every item contains the results for
 * the related geometry. These results are stored as a Map<Integer, Map<Integer, Statistics[]>. The inner map contains
 * the results for every zone indicated by the optional classifier object (if the classifier is not present, the
 * statistics are stored inside the 0-key item). The outer map contains the results for every band. The user must only
 * select the band, the zone(if present) and the index associated to the desired Statistics object and then call the
 * getResult() method for having the result.
 *
 * <p>
 *
 * <table border=1>
 * <caption>Resource List</caption>
 * <tr>
 * <th>Name</th>
 * <th>Value</th>
 * </tr>
 * <tr>
 * <td>GlobalName</td>
 * <td>Zonal</td>
 * </tr>
 * <tr>
 * <td>LocalName</td>
 * <td>Zonal</td>
 * </tr>
 * <tr>
 * <td>Vendor</td>
 * <td>org.eclipse.imagen.media</td>
 * </tr>
 * <tr>
 * <td>Description</td>
 * <td>Image operator for calculating statistics on different image zones supporting ROI and No Data.</td>
 * </tr>
 * <tr>
 * <td>DocURL</td>
 * <td>Not Defined</td>
 * </tr>
 * <tr>
 * <td>Version</td>
 * <td>1.0</td>
 * </tr>
 * <tr>
 * <td>arg0Desc</td>
 * <td>Classifier image.</td>
 * </tr>
 * <tr>
 * <td>arg1Desc</td>
 * <td>Transformation object used for mapping the Source image to the classifier.</td>
 * </tr>
 * <tr>
 * <td>arg2Desc</td>
 * <td>List of all the geometries to analyze.</td>
 * </tr>
 * <tr>
 * <td>arg3Desc</td>
 * <td>No Data Range used.</td>
 * </tr>
 * <tr>
 * <td>arg4Desc</td>
 * <td>ROI Object used.</td>
 * </tr>
 * <tr>
 * <td>arg5Desc</td>
 * <td>Boolean indicatin if the ROI RasterAccessor should be used during computations.</td>
 * </tr>
 * <tr>
 * <td>arg6Desc</td>
 * <td>Array containing the indexes of the bands to calculate.</td>
 * </tr>
 * <tr>
 * <td>arg7Desc</td>
 * <td>Array indicating which statistical operations must be performed on all the selected bands.</td>
 * </tr>
 * <tr>
 * <td>arg8Desc</td>
 * <td>Array indicating the minimum bounds for complex statistics on all the selected bands.</td>
 * </tr>
 * <tr>
 * <td>arg9Desc</td>
 * <td>Array indicating the maximum bounds for complex statistics on all the selected bands.</td>
 * </tr>
 * <tr>
 * <td>arg10Desc</td>
 * <td>Array indicating the number of bins for complex statistics on all the selected bands.</td>
 * </tr>
 * <tr>
 * <td>arg11Desc</td>
 * <td>List of the possible ranges for dividing the statistics.</td>
 * </tr>
 * <tr>
 * <td>arg12Desc</td>
 * <td>Boolean indicating if the results must be calculated for each range.</td>
 * </tr>
 * </table>
 *
 * <p>
 *
 * <table border=1>
 * <caption>Parameter List</caption>
 * <tr>
 * <th>Name</th>
 * <th>Class Type</th>
 * <th>Default Value</th>
 * </tr>
 * <tr>
 * <td>classifier</td>
 * <td>RenderedImage</td>
 * <td>null</td>
 * <tr>
 * <td>transform</td>
 * <td>AffineTransform</td>
 * <td>null</td>
 * <tr>
 * <td>roilist</td>
 * <td>List</td>
 * <td>null</td>
 * <tr>
 * <td>noData</td>
 * <td>org.eclipse.imagen.media.range.Range</td>
 * <td>null</td>
 * <tr>
 * <td>mask</td>
 * <td>org.eclipse.imagen.ROI</td>
 * <td>null</td>
 * <tr>
 * <td>useROIAccessor</td>
 * <td>Boolean</td>
 * <td>false</td>
 * <tr>
 * <td>bands</td>
 * <td>int[]</td>
 * <td>null</td>
 * <tr>
 * <td>stats</td>
 * <td>org.eclipse.imagen.media.stats.Statistics.StatsType[]</td>
 * <td>null</td>
 * <tr>
 * <td>minbound</td>
 * <td>double[]</td>
 * <td>null</td>
 * <tr>
 * <td>maxbound</td>
 * <td>double[]</td>
 * <td>null</td>
 * <tr>
 * <td>numbin</td>
 * <td>int[]</td>
 * <td>null</td>
 * <tr>
 * <td>rangeData</td>
 * <td>List</td>
 * <td>null</td>
 * <tr>
 * <td>localStats</td>
 * <td>Boolean</td>
 * <td>false</td>
 * </table>
 */
public class ZonalStatsDescriptor extends OperationDescriptorImpl {

    /** Zonal Statistics property name */
    public static final String ZS_PROPERTY = "JAI-EXT.zonalstats";

    /**
     * The resource strings that provide the general documentation and specify the parameter list for this operation.
     */
    private static final String[][] resources = {
        {"GlobalName", "Zonal"},
        {"LocalName", "Zonal"},
        {"Vendor", "org.eclipse.imagen.media"},
        {"Description", "Image operator for calculating statistics on various Geometries supporting ROI and No Data"},
        {"DocURL", "Not defined"},
        {"Version", "1.0"},
        {"arg0Desc", "Classifier image"},
        {"arg1Desc", "Transformation object used for mapping the Source image to the classifier"},
        {"arg2Desc", "List of all the geometries to analyze"},
        {"arg3Desc", "No Data Range used"},
        {"arg4Desc", "ROI Object used"},
        {"arg5Desc", "Boolean indicatin if the ROI RasterAccessor should be used during computations"},
        {"arg6Desc", "Array containing the indexes of the bands to calculate"},
        {"arg7Desc", "Array indicating which statistical operations must be performed on all the selected bands"},
        {"arg8Desc", "Array indicating the minimum bounds for complex statistics on all the selected bands"},
        {"arg9Desc", "Array indicating the maximum bounds for complex statistics on all the selected bands"},
        {"arg10Desc", "Array indicating the number of bins for complex statistics on all the selected bands"},
        {"arg11Desc", "List of the possible ranges for dividing the statistics"},
        {"arg12Desc", "Boolean indicating if the results must be calculated for each range"}
    };

    /** The parameter class list for this operation. */
    private static final Class[] paramClasses = {
        RenderedImage.class,
        AffineTransform.class,
        java.util.List.class,
        org.eclipse.imagen.media.range.Range.class,
        org.eclipse.imagen.ROI.class,
        Boolean.class,
        int[].class,
        org.eclipse.imagen.media.stats.Statistics.StatsType[].class,
        double[].class,
        double[].class,
        int[].class,
        java.util.List.class,
        Boolean.class
    };

    /** The parameter name list for this operation. */
    private static final String[] paramNames = {
        "classifier",
        "transform",
        "roilist",
        "noData",
        "mask",
        "useROIAccessor",
        "bands",
        "stats",
        "minbound",
        "maxbound",
        "numbin",
        "rangeData",
        "localStats"
    };

    /** The parameter default value list for this operation. */
    private static final Object[] paramDefaults = {
        null, null, null, null, null, false, new int[] {0}, null, null, null, null, null, false
    };

    public ZonalStatsDescriptor() {
        super(resources, 1, paramClasses, paramNames, paramDefaults);
    }

    /**
     * Performs statistical operations on different image zones defined by the input geometry list.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param source <code>RenderedImage</code> source image.
     * @param classifier <code>RenderedImage</code> optional classifier image(Integral dataType).
     * @param transform affine transformation used for mapping source image on the classifier.
     * @param roilist list of all the geometries.
     * @param NoData No Data range used for calculation.
     * @param mask optional mask for reducing the computations on a selected ROI.
     * @param useROIAccessor boolean indicating if a ROI RasterAccessor should be used during computations with the
     *     mask.
     * @param bands Array indicating which band to consider.
     * @param stats Array indicating which statistics to consider.
     * @param minBound Array indicating minimum bounds for complex computations.
     * @param maxBound Array indicating maximum bounds for complex computations.
     * @param numBins Array indicating the number of bins for complex computations.
     * @param rangeData List of the possible range to calculate the statistics.
     * @param localStats Boolean indicating if the statistics must be stored for each range.
     * @param hints The <code>RenderingHints</code> to use.
     * @return The <code>RenderedOp</code> source image.
     * @throws IllegalArgumentException if <code>source</code> is <code>null</code>.
     */
    public static RenderedOp create(
            RenderedImage source,
            RenderedImage classifier,
            AffineTransform transform,
            List<ROI> roilist,
            Range noData,
            ROI mask,
            boolean useROIAccessor,
            int[] bands,
            StatsType[] stats,
            double[] minBound,
            double[] maxBound,
            int[] numBins,
            List<Range> rangeData,
            boolean localStats,
            RenderingHints hints) {
        // Creation of a parameterBlockJAI containing all the operation parameters
        ParameterBlockJAI pb = new ParameterBlockJAI("Zonal", RenderedRegistryMode.MODE_NAME);
        // Source image
        pb.setSource(source, 0);
        // Image parameters
        pb.setParameter("classifier", classifier);
        pb.setParameter("transform", transform);
        pb.setParameter("roilist", roilist);
        pb.setParameter("NoData", noData);
        pb.setParameter("mask", mask);
        pb.setParameter("useROIAccessor", useROIAccessor);
        pb.setParameter("bands", bands);
        pb.setParameter("stats", stats);
        pb.setParameter("minbound", minBound);
        pb.setParameter("maxbound", maxBound);
        pb.setParameter("numbin", numBins);
        pb.setParameter("rangeData", rangeData);
        pb.setParameter("localStats", localStats);

        // RenderedImage creation
        return JAI.create("Zonal", pb, hints);
    }

    /**
     * Performs statistical operations on different image zones defined by the input geometry list.
     *
     * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @see JAI
     * @see ParameterBlockJAI
     * @see RenderedOp
     * @param source <code>RenderedImage</code> source image.
     * @param classifier <code>RenderedImage</code> optional classifier image(Integral dataType).
     * @param transform affine transformation used for mapping source image on the classifier.
     * @param roilist list of all the geometries.
     * @param NoData No Data range used for calculation.
     * @param mask optional mask for reducing the computations on a selected ROI.
     * @param useROIAccessor boolean indicating if a ROI RasterAccessor should be used during computations with the
     *     mask.
     * @param bands Array indicating which band to consider.
     * @param stats Array indicating which statistics to consider.
     * @param rangeData List of the possible range to calculate the statistics.
     * @param localStats Boolean indicating if the statistics must be stored for each range.
     * @return The <code>RenderedOp</code> source image.
     * @throws IllegalArgumentException if <code>source</code> is <code>null</code>.
     */
    public static RenderedOp create(
            RenderedImage source,
            RenderedImage classifier,
            AffineTransform transform,
            List<ROI> roilist,
            Range noData,
            ROI mask,
            boolean useROIAccessor,
            int[] bands,
            StatsType[] stats,
            List<Range> rangeData,
            boolean localStats,
            RenderingHints hints) {
        return create(
                source,
                classifier,
                transform,
                roilist,
                noData,
                mask,
                useROIAccessor,
                bands,
                stats,
                null,
                null,
                null,
                rangeData,
                localStats,
                hints);
    }
}
